﻿using System;
using System.Collections;
using System.Configuration;
using System.Web;

namespace Navigation
{
	/// <summary>
	/// Persists crumb trails, over a specified length, in <see cref="System.Web.HttpContext.Session"/> on
	/// the Web server. Prevents the creation of unmanageably long Urls
	/// </summary>
	public class SessionCrumbTrailPersister : CrumbTrailPersister
	{
		private const string SESSION_PREFIX = "0";
		private const string CRUMB_TRAIL_QUEUE = "CRUMBTRAILQUEUE";

		/// <summary>
		/// Gets of sets the length above which any crumb will be stored on the Web server, the default 
		/// value is 500
		/// </summary>
		[IntegerValidator(MinValue = 0), ConfigurationProperty("maxLength", DefaultValue = 500)]
		public int MaxLength
		{
			get
			{
				return (int) this["maxLength"];
			}
			set
			{
				this["maxLength"] = value;
			}
		}

		/// <summary>
		/// Gets of sets the maximum number of crumb trails that will be held at any one time on the 
		/// Web server, the default value is 50
		/// </summary>
		[IntegerValidator(MinValue = 1), ConfigurationProperty("historySize", DefaultValue = 50)]
		public int HistorySize
		{
			get
			{
				return (int)base["historySize"];
			}
			set
			{
				base["historySize"] = value;
			}
		}

		/// <summary>
		/// Uses the <paramref name="crumbTrail"/> parameter to determine whether to retrieve the crumb
		/// trail from <see cref="System.Web.HttpContext.Session"/>. If retrieved from session it will be 
		/// null if it had been removed as a result of the <see cref="HistorySize"/> being breached or 
		/// if the session expired since the crumb trail was added
		/// </summary>
		/// <param name="crumbTrail">Key generated by the <see cref="Save"/> method</param>
		/// <returns>Either the <paramref name="crumbTrail"/> or the retrieved value from server side session; can
		/// be null if retrieved from session
		/// </returns>
		public override string Load(string crumbTrail)
		{
			if (crumbTrail != null && crumbTrail.Length > 0)
			{
				if (crumbTrail.Substring(0, 1) == SESSION_PREFIX)
				{
					return (string) HttpContext.Current.Session[crumbTrail];
				}
			}
			return crumbTrail;
		}

		/// <summary>
		/// If the <paramref name="crumbTrail"/> is not over the <see cref="MaxLength"/> it is returned.
		/// Otherwise the <paramref name="crumbTrail"/> is stored in session using a short key, unique
		/// within a given session. Also expunges old items from session, if the <see cref="HistorySize"/>
		/// is breached when a new item is added
		/// </summary>
		/// <param name="crumbTrail">The crumb trail to persist</param>
		/// <returns><paramref name="crumbTrail"/> or short, generated key pointing at <paramref name="crumbTrail"/>
		/// in session</returns>
		public override string Save(string crumbTrail)
		{
			if (crumbTrail != null && crumbTrail.Length == 0)
			{
				crumbTrail = null;
			}
			if (crumbTrail != null && crumbTrail.Length > MaxLength)
			{
				long current = DateTime.Now.Ticks;
				ArrayList list = HttpContext.Current.Session[CRUMB_TRAIL_QUEUE] as ArrayList;
				if (list == null)
				{
					list = new ArrayList();
					HttpContext.Current.Session[CRUMB_TRAIL_QUEUE] = list;
				}
				if (list.Count > 0)
				{
					string latestKey = (string)list[list.Count - 1];
					string latestCrumbTrail = (string)HttpContext.Current.Session[latestKey];
					if (crumbTrail == latestCrumbTrail)
					{
						return latestKey;
					}
					long latest = Convert.ToInt64(latestKey.Substring(SESSION_PREFIX.Length), 16);
					if (current <= latest)
					{
						current = latest + 1;
					}
				}
				string key = SESSION_PREFIX + Convert.ToString(current, 16);
				HttpContext.Current.Session[key] = crumbTrail;
				list.Add(key);
				if (list.Count > HistorySize)
				{
					HttpContext.Current.Session.Remove((string)list[0]);
					list.RemoveAt(0);
				}
				return key;
			}
			return crumbTrail;
		}
	}
}
